/*    file: main.cpp
 *    desc:
 * 
 * created: 2016-04-06
 *  author: chuanjiang.zh@qq.com
 * company: 
 */

#include <stdio.h>
#include <iostream>

#include "BasicType.h"
#include "RtpCaster.h"
#include "Ffmpeg.h"
#include "SharedPtr.h"
#include "TFileUtil.h"
#include <assert.h>

#include "LiveDevice.h"
#include "RtspCaster.h"

#include "TThread.h"
#include "TEvent.h"


extern "C"
{
#include "libavutil/time.h"
}

static bool hasPrefix(const uint8_t* data, int size)
{
    assert(size >= 4);
    if ((data[0] == 0) && (data[1] == 0))
    {
        if ((data[2] == 0) && (data[3] == 1))
        {
            return true;
        }
        if (data[2] == 1)
        {
            return true;
        }
    }
    return false;
}

struct CasterGuard
{
public:
    CasterGuard()
    {
        av::LiveDevice::startup();
    }

    ~CasterGuard()
    {
        av::LiveDevice::cleanup();
    }
};


class MediaDeviceSource : public comn::Thread
{
public:

    MediaDeviceSource(av::LiveDevice& dev, int chl):
      m_dev(dev),
      m_chl(chl),
      m_fmtContext(),
      m_fmt(),
      m_videoIndex(-1),
      m_audioIndex(-1)
    {
        memset(&m_fmt, 0, sizeof(m_fmt));
    }

    ~MediaDeviceSource()
    {

    }

    int open(const std::string& url)
    {
        int rc = avformat_open_input(&m_fmtContext, url.c_str(), NULL, NULL);
        if (rc != 0)
        {
            return EBADF;
        }

        rc = avformat_find_stream_info(m_fmtContext, NULL);
        if (rc != 0)
        {
            return rc;
        }

        m_url = url;

        AVCodecContext* vContext = 0;
        AVCodec* videoCodec = NULL;
        {
            int idx  = av_find_best_stream(m_fmtContext, AVMEDIA_TYPE_VIDEO, -1, -1, &videoCodec, 0);
            if (videoCodec)
            {
                AVStream* stream = m_fmtContext->streams[idx];
                vContext = stream->codec;
                m_fmt.width = vContext->width;
                m_fmt.height = vContext->height;
                m_fmt.codec = vContext->codec_id;
                m_fmt.profile = vContext->profile;
                m_fmt.clockRate = vContext->time_base.den;

                m_fmt.vPropSize = vContext->extradata_size;
                m_fmt.vProp = vContext->extradata;

                if (stream->avg_frame_rate.den > 0)
                {
                    m_fmt.framerate = stream->avg_frame_rate.num / stream->avg_frame_rate.den;
                }
                else if (stream->r_frame_rate.den > 0)
                {
                    m_fmt.framerate = stream->r_frame_rate.num / stream->r_frame_rate.den;
                }
                else 
                {
                    m_fmt.framerate = 25;
                }

                m_videoIndex = idx;

            }

        }

        AVCodecContext* aContext = 0;
        AVCodec* audioCodec = NULL;
        {
            int idx = av_find_best_stream(m_fmtContext, AVMEDIA_TYPE_AUDIO, -1, -1, &audioCodec, 0);
            if (audioCodec)
            {
                AVStream* stream = m_fmtContext->streams[idx];
                aContext = stream->codec;

                m_fmt.audioCodec = aContext->codec_id;
                m_fmt.sampleRate = aContext->sample_rate;
                m_fmt.channels = aContext->channels;
                //m_fmt.audioProfile = aContext->profile;

                m_fmt.sampleRate = stream->time_base.den;

                m_audioIndex = idx;
            }
        }

        m_dev.setVideoFormat(m_chl, m_fmt.width, m_fmt.height, m_fmt.framerate, m_fmt.codec);

        if (m_fmt.audioCodec > 0)
        {
            m_dev.setAudioFormat(m_chl, m_fmt.channels, m_fmt.sampleRate, m_fmt.audioCodec);
        }

        start();

        return 0;
    }

    void close()
    {
        if (isRunning())
        {
            stop();
        }

        if (m_fmtContext)
        {
            avformat_close_input(&m_fmtContext);
        }
    }

protected:
    virtual int run()
    {
        while (!m_canExit)
        {
            int rc = 0;

            AVRational avbase = av_make_q(1, AV_TIME_BASE);

            AVPacket pkt;
            av_init_packet(&pkt);

            int64_t tmStart = av_gettime_relative();
            int64_t videoDuration = 0;
            int64_t audioDuration = 0;

            while (!m_canExit)
            {
                rc = av_read_frame(m_fmtContext, &pkt);
                if (rc != 0)
                {
                    avformat_close_input(&m_fmtContext);

                    avformat_open_input(&m_fmtContext, m_url.c_str(), NULL, NULL);
                    avformat_find_stream_info(m_fmtContext, NULL);

                    continue;
                }

                int delay = 0;
                if (pkt.stream_index == m_videoIndex)
                {
                    AVStream* stream = m_fmtContext->streams[pkt.stream_index];
                    av_packet_rescale_ts(&pkt, stream->time_base, avbase);
                    int duration = pkt.duration;
                    videoDuration += duration;

                    int64_t clockDuration = av_gettime_relative() - tmStart;
                    delay = videoDuration - clockDuration;

                    if (!hasPrefix(pkt.data, pkt.size))
                    {
                        *(int*)pkt.data = 0x01000000;
                    }

                    int64_t pts = pkt.pts;
                    if (pts == AV_NOPTS_VALUE)
                    {
                        pts = tmStart + videoDuration;
                    }

                    m_dev.inputVideo(m_chl, pkt.data, pkt.size, pts);

                    //comn::FileUtil::write(pkt.data, pkt.size, "m.hevc", true);
                    //printf("video size:%d, duration:%d, pts:%I64d\n", pkt.size, pkt.duration, pkt.pts/1000);
                }
                else if (pkt.stream_index == m_audioIndex)
                {
                    AVStream* stream = m_fmtContext->streams[pkt.stream_index];
                    av_packet_rescale_ts(&pkt, stream->time_base, avbase);

                    int duration = pkt.duration;
                    audioDuration += duration;
                    int64_t clockDuration = av_gettime_relative() - tmStart;
                    delay = audioDuration - clockDuration;

                    m_dev.inputAudio(m_chl, pkt.data, pkt.size, pkt.pts);

                    //printf("audio size:%d, duration:%d, pts:%I64d\n", pkt.size, pkt.duration, pkt.pts/1000);
                }

				av_packet_unref(&pkt);

                //printf("delay: %f ms\n", delay / 1000.0);
                if (delay > 0)
                {
                    //av_usleep(delay);
                }

            }
        }

        return 0;
    }

    virtual void doStop() 
    {
        m_event.post();
    }


protected:
    av::LiveDevice& m_dev;
    int m_chl;

    AVFormatContext* m_fmtContext;
    MFormat m_fmt;
    
    std::string m_url;

    comn::Event m_event;
    
    int m_videoIndex;
    int m_audioIndex;

};


int main(int argc, char** argv)
{
	av_register_all();
    avformat_network_init();
	
	static const int CHANNEL_COUNT = 2;
	std::string urls[CHANNEL_COUNT] = {
		"rtsp://admin:sjld16301@192.168.3.65/",
		"rtsp://admin:sjld16301@192.168.3.65/h264/ch1/sub/av_stream"
	};

	if (argc > 2)
    {
		urls[0] = argv[1];
		urls[1] = argv[2];
    }

    CasterGuard casterGuard;
    

    av::LiveDevice& device = av::LiveDevice::instance();

	device.setLocalIp("192.168.2.33");
	device.setServer("192.168.2.247", "demo", "123456");

    int rc = device.open(CHANNEL_COUNT);
    assert(rc == 0);

    typedef std::shared_ptr< MediaDeviceSource >        MediaDeviceSourcePtr;
    MediaDeviceSourcePtr deviceSourceArray[2];
    for (int i = 0; i < CHANNEL_COUNT; i ++)
    {
        deviceSourceArray[i].reset(new MediaDeviceSource(device, i));
        deviceSourceArray[i]->open(urls[i]);
    }

    std::string line;
    while (true)
    {
        std::cout << ">";
        std::getline(std::cin, line);
        if (line == "q" || line == "quit")
        {
            break;
        }
        else
        {
            //
        }
    }

    for (int i = 0; i < CHANNEL_COUNT; i ++)
    {
        deviceSourceArray[i]->close();
        deviceSourceArray[i].reset();
    }

    device.close();

	return 0;
}




